---
title: Extension Guide
---

```mdx-code-block
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
```

The Token-2022 program provides additional functionality on mints and token
accounts through an extension model.

This guide explains all of the available extensions, along with some examples of
how to use them.

Please see the [Token-2022 Introduction](../token-2022) for more general information
about Token-2022 and the concept of extensions.

## Setup

See the [Token Setup Guide](../token#setup) to install the client utilities.
Token-2022 shares the same CLI and NPM packages for maximal compatibility.

All JS examples are adapted from the tests, and available in full at the
[Token JS examples](https://github.com/solana-labs/solana-program-library/tree/master/token/js/examples).

## Extensions

### Mint Close Authority

The Token program allows owners to close token accounts, but it is impossible
to close mint accounts. In Token-2022, it is possible to close mints by initializing
the `MintCloseAuthority` extension before initializing the mint.

#### Example: Initializing a mint with mint close authority

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --enable-close
Creating token C47NXhUTVEisCfX7s16KrxYyimnui7HpUXZecE2TmLdB under program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
import {
    closeAccount,
    createInitializeMintInstruction,
    createInitializeMintCloseAuthorityInstruction,
    getMintLen,
    ExtensionType,
    TOKEN_2022_PROGRAM_ID,
} from '@solana/spl-token';
import {
    clusterApiUrl,
    sendAndConfirmTransaction,
    Connection,
    Keypair,
    SystemProgram,
    Transaction,
    LAMPORTS_PER_SOL,
} from '@solana/web3.js';

(async () => {
    const payer = Keypair.generate();

    const mintKeypair = Keypair.generate();
    const mint = mintKeypair.publicKey;
    const mintAuthority = Keypair.generate();
    const freezeAuthority = Keypair.generate();
    const closeAuthority = Keypair.generate();

    const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');

    const airdropSignature = await connection.requestAirdrop(payer.publicKey, 2 * LAMPORTS_PER_SOL);
    await connection.confirmTransaction({ signature: airdropSignature, ...(await connection.getLatestBlockhash()) });

    const extensions = [ExtensionType.MintCloseAuthority];
    const mintLen = getMintLen(extensions);
    const lamports = await connection.getMinimumBalanceForRentExemption(mintLen);

    const transaction = new Transaction().add(
        SystemProgram.createAccount({
            fromPubkey: payer.publicKey,
            newAccountPubkey: mint,
            space: mintLen,
            lamports,
            programId: TOKEN_2022_PROGRAM_ID,
        }),
        createInitializeMintCloseAuthorityInstruction(mint, closeAuthority.publicKey, TOKEN_2022_PROGRAM_ID),
        createInitializeMintInstruction(
            mint,
            9,
            mintAuthority.publicKey,
            freezeAuthority.publicKey,
            TOKEN_2022_PROGRAM_ID
        )
    );
    await sendAndConfirmTransaction(connection, transaction, [payer, mintKeypair], undefined);
})();
```

  </TabItem>
</Tabs>

#### Example: Closing a mint

With the `MintCloseAuthority` extension on the mint and a valid authority, it's
possible to close the mint account and reclaim the lamports on the mint account.
**Note**: The supply on the mint must be 0.

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token close-mint C47NXhUTVEisCfX7s16KrxYyimnui7HpUXZecE2TmLdB 
Signature: 5nidwS9fJGJGdmaQjcwvNGVtk2ba5Zyu9ZLubjUKSsaAyzLUYvB6LK5RfUA767veBr45x7R1WW9N7WkYZ3Rqsb5B
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
await closeAccount(connection, payer, mint, payer.publicKey, closeAuthority, [], undefined, TOKEN_2022_PROGRAM_ID);
```

  </TabItem>
</Tabs>

### Transfer Fees

In the Token program, it is impossible to assess a fee on every transfer. The
existing systems typically involve freezing user accounts, and forcing them to go
through a third party to unfreeze, transfer, and refreeze the accounts.

With Token-2022, it's possible to configure a transfer fee on a mint so that fees
are assessed at the protocol level. On every transfer, some amount is withheld
on the recipient account, untouchable by the recipient. These tokens can be
withheld by a separate authority on the mint.

**Important note**: Transferring tokens with a transfer fee requires using
`transfer_checked` or `transfer_checked_with_fee` instead of `transfer`.
Otherwise, the transfer will fail.

#### Example: Creating a mint with a transfer fee

Transfer fee configurations contain a few important fields:

* Fee in basis points: fee assessed on every transfer, as basis points of
  the transfer amount. For example, with 50 basis points, a transfer of 1,000
  tokens yields 5 tokens
* Maximum fee: cap on transfer fees. With a maximum fee of 5,000 tokens, even
  a transfer of 10,000,000,000,000 tokens only yields 5,000 tokens
* Transfer fee authority: entity that can modify the fees
* Withdraw withheld authority: entity that can move tokens withheld on the
  mint or token accounts

Let's create a mint with 50 basis point transfer fee, and a maximum fee of 5,000
tokens.

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --transfer-fee-basis-points 50 --transfer-fee-maximum-fee 5000
Creating token Dg3i18BN7vzsbAZDnDv3H8nQQjSaPUTqhwX41J7NZb5H under program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

Address:  Dg3i18BN7vzsbAZDnDv3H8nQQjSaPUTqhwX41J7NZb5H
Decimals:  9

Signature: 39okFGqW23wQZ1HqH2tdJvtFP5aYgpfbmNktCZpV5XKTpKuA9xJmvBmrBwcLdfAT632VEC4y4dJJfDoeAvMWRPYP
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
import {
    clusterApiUrl,
    sendAndConfirmTransaction,
    Connection,
    Keypair,
    SystemProgram,
    Transaction,
    LAMPORTS_PER_SOL,
} from '@solana/web3.js';

import {
    ExtensionType,
    createInitializeMintInstruction,
    mintTo,
    createAccount,
    getMintLen,
    TOKEN_2022_PROGRAM_ID,
} from '@solana/spl-token';

import {
    createInitializeTransferFeeConfigInstruction,
    harvestWithheldTokensToMint,
    transferCheckedWithFee,
    withdrawWithheldTokensFromAccounts,
    withdrawWithheldTokensFromMint,
} from '@solana/spl-token';

(async () => {
    const payer = Keypair.generate();

    const mintAuthority = Keypair.generate();
    const mintKeypair = Keypair.generate();
    const mint = mintKeypair.publicKey;
    const transferFeeConfigAuthority = Keypair.generate();
    const withdrawWithheldAuthority = Keypair.generate();

    const extensions = [ExtensionType.TransferFeeConfig];

    const mintLen = getMintLen(extensions);
    const decimals = 9;
    const feeBasisPoints = 50;
    const maxFee = BigInt(5_000);

    const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');

    const airdropSignature = await connection.requestAirdrop(payer.publicKey, 2 * LAMPORTS_PER_SOL);
    await connection.confirmTransaction({ signature: airdropSignature, ...(await connection.getLatestBlockhash()) });

    const mintLamports = await connection.getMinimumBalanceForRentExemption(mintLen);
    const mintTransaction = new Transaction().add(
        SystemProgram.createAccount({
            fromPubkey: payer.publicKey,
            newAccountPubkey: mint,
            space: mintLen,
            lamports: mintLamports,
            programId: TOKEN_2022_PROGRAM_ID,
        }),
        createInitializeTransferFeeConfigInstruction(
            mint,
            transferFeeConfigAuthority.publicKey,
            withdrawWithheldAuthority.publicKey,
            feeBasisPoints,
            maxFee,
            TOKEN_2022_PROGRAM_ID
        ),
        createInitializeMintInstruction(mint, decimals, mintAuthority.publicKey, null, TOKEN_2022_PROGRAM_ID)
    );
    await sendAndConfirmTransaction(connection, mintTransaction, [payer, mintKeypair], undefined);
})();
```

  </TabItem>
</Tabs>

#### Example: Transferring tokens with the fee checked

As part of the extension, there is a new `transfer_checked_with_fee` instruction,
which accepts the expected fee. The transfer only succeeds if the fee is correctly
calculated, in order to avoid any surprises during the transfer.

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token create-account Dg3i18BN7vzsbAZDnDv3H8nQQjSaPUTqhwX41J7NZb5H
Creating account 7UKuG4W68hW9eGrDms6BenRf8DCEHKGN49xewtWyB5cx

Signature: 6h591BMuguh9TtSdQPRPcPy97mLqJiybeaxGVZzD8mvPEsYypjZ2jjKgHzji5FGh8CJE3NAzqrqGxfyMdnbWrs7
$ solana-keygen new -o destination.json
$ spl-token create-account Dg3i18BN7vzsbAZDnDv3H8nQQjSaPUTqhwX41J7NZb5H destination.json
Creating account 5wY8fiMZG5wGbQmtzKgqqEEp4vsCMJZ53RXEagUUWhEr

Signature: 2SyA17AJRWLH2j7svgxgW7nouUGioeWoRDWjz2Wq8j1eisThezSvqgN4NbHfj9uWmDh2XRp56ttZtHV1SxaUC7ys
$ spl-token mint Dg3i18BN7vzsbAZDnDv3H8nQQjSaPUTqhwX41J7NZb5H 1000000000
Minting 1000000000 tokens
  Token: Dg3i18BN7vzsbAZDnDv3H8nQQjSaPUTqhwX41J7NZb5H
  Recipient: 7UKuG4W68hW9eGrDms6BenRf8DCEHKGN49xewtWyB5cx

Signature: 5MFJGpLaWe3yLLU8X4ax3KofeqPVzdxJsa3ScjChJJHJawKsRx4og9eaFkWn3CPF7JXaxdj5v4LdAW56LiNTuP6s
$ spl-token transfer --expected-fee 0.000005 Dg3i18BN7vzsbAZDnDv3H8nQQjSaPUTqhwX41J7NZb5H 1000000 destination.json
Transfer 1000000 tokens
  Sender: 7UKuG4W68hW9eGrDms6BenRf8DCEHKGN49xewtWyB5cx
  Recipient: 5wY8fiMZG5wGbQmtzKgqqEEp4vsCMJZ53RXEagUUWhEr

Signature: 3hc3CCiETiuCArJ6yZ76ScyfMeK1rw8CTfZ3aDGnYoEMeoqXfSNAtnM3ATFjm7UihthzEkEWzeUfWL4qqqB4ofgv
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
    const mintAmount = BigInt(1_000_000_000);
    const owner = Keypair.generate();
    const sourceAccount = await createAccount(
        connection,
        payer,
        mint,
        owner.publicKey,
        undefined,
        undefined,
        TOKEN_2022_PROGRAM_ID
    );
    await mintTo(
        connection,
        payer,
        mint,
        sourceAccount,
        mintAuthority,
        mintAmount,
        [],
        undefined,
        TOKEN_2022_PROGRAM_ID
    );

    const accountKeypair = Keypair.generate();
    const destinationAccount = await createAccount(
        connection,
        payer,
        mint,
        owner.publicKey,
        accountKeypair,
        undefined,
        TOKEN_2022_PROGRAM_ID
    );

    const transferAmount = BigInt(1_000_000);
    const fee = (transferAmount * BigInt(feeBasisPoints)) / BigInt(10_000);
    await transferCheckedWithFee(
        connection,
        payer,
        sourceAccount,
        mint,
        destinationAccount,
        owner,
        transferAmount,
        decimals,
        fee,
        [],
        undefined,
        TOKEN_2022_PROGRAM_ID
    );
```

  </TabItem>
</Tabs>

#### Example: Find accounts with withheld tokens

As users transfer their tokens, transfer fees accumulate in the various recipient
accounts. The withdraw withheld authority, configured at initialization, can move
these tokens wherever they wish using `withdraw_withheld_tokens_from_accounts` or
`harvest_withheld_tokens_to_mint`.

Before doing that, however, they must find which accounts have withheld tokens
by iterating over all accounts for the mint.

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

CLI support coming soon!

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
    const allAccounts = await connection.getProgramAccounts(TOKEN_2022_PROGRAM_ID, {
        commitment: 'confirmed',
        filters: [
            {
                memcmp: {
                    offset: 0,
                    bytes: mint.toString(),
                },
            },
        ],
    });
    const accountsToWithdrawFrom = [];
    for (const accountInfo of allAccounts) {
        const account = unpackAccount(accountInfo.account, accountInfo.pubkey, TOKEN_2022_PROGRAM_ID);
        const transferFeeAmount = getTransferFeeAmount(account);
        if (transferFeeAmount !== null && transferFeeAmount.withheldAmount > BigInt(0)) {
            accountsToWithdrawFrom.push(accountInfo.pubkey);
        }
    }
```

  </TabItem>
</Tabs>

#### Example: Withdraw withheld tokens from accounts

With the accounts found, the withheld withdraw authority may move the withheld
tokens.

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token withdraw-withheld-tokens 7UKuG4W68hW9eGrDms6BenRf8DCEHKGN49xewtWyB5cx 5wY8fiMZG5wGbQmtzKgqqEEp4vsCMJZ53RXEagUUWhEr
Signature: 2NfjbEnRQC7kXkf86stb6u7eUtaQTGDebo8ktCdz4gP4wCD93xtx75rSJxJDQVePNAa8NqtVLjUm19ZBDRVaYurt
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
    await withdrawWithheldTokensFromAccounts(
        connection,
        payer,
        mint,
        destinationAccount,
        withdrawWithheldAuthority,
        [],
        [destinationAccount],
        undefined,
        TOKEN_2022_PROGRAM_ID
    );
```

  </TabItem>
</Tabs>

**Note**: The design of pooling transfer fees at the recipient account is meant to
maximize parallelization of transactions. Otherwise, one configured fee recipient
account would be write-locked between parallel transfers, decreasing throughput
of the protocol.

#### Example: Harvest withheld tokens to mint

Users may want to close a token account with withheld transfer fees, but it is
impossible to close an account that holds any tokens, including withheld ones.

To clear out their account of withheld tokens, they can use the permissionless
`harvest_withheld_tokens_to_mint` instruction.

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

The harvest instruction isn't explicitly exposed since it typically isn't needed.
It is required before closing an account, however, so we can show the harvest
behavior by closing the account:

```console
$ spl-token close --address 5wY8fiMZG5wGbQmtzKgqqEEp4vsCMJZ53RXEagUUWhEr
Signature: KAKXryAdGSVFqpQhrwrvP6NCAQwLQp2Sj1WiAqCHxxwJsvRLKx4JzWgN9zYUaJNmfrZnQQw9yYoDw5Xx1YrwY6i

Signature: 2i5KGekFFtwzkX2W71cxPvQsGEH21qmZ3ieNQz7Mz2qGqp2pyzMNZhSVRfxJxQuAxnKQoZKjAb62FBx2gxaq25Le
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
    await harvestWithheldTokensToMint(connection, payer, mint, [destinationAccount], undefined, TOKEN_2022_PROGRAM_ID);
```

  </TabItem>
</Tabs>

#### Example: Withdraw withheld tokens from mint

As users move the withheld tokens to the mint, the withdraw authority
may choose to move those tokens from the mint to any other account.

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token withdraw-withheld-tokens --include-mint 7UKuG4W68hW9eGrDms6BenRf8DCEHKGN49xewtWyB5cx

Signature: 5KzdgcKgi3rLaBRfDbG5pxZwyKppyVjAA8TUCjTMfb1vMYv7CLQWaxgFz81jz4reUaF7oP67Gdqoc91Ted6qr1Hb
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
    await withdrawWithheldTokensFromMint(
        connection,
        payer,
        mint,
        destinationAccount,
        withdrawWithheldAuthority,
        [],
        undefined,
        TOKEN_2022_PROGRAM_ID
    );
```

  </TabItem>
</Tabs>

### Default Account State

A mint creator may want to restrict who can use their token. There are many
heavy-handed approaches to this problem, most of which include going through a
centralized service at the beginning. Even through a centralized service, however,
it's possible for anyone to create a new token account and transfer the tokens
around.

To simplify the restriction, a mint creator may use the `DefaultAccountState`
extension, which can force all new token accounts to be frozen. This way, users
must eventually interact with some service to unfreeze their account and use
tokens.

#### Example: Creating a mint with default frozen accounts

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --enable-freeze --default-account-state frozen
Creating token 8Sqz2zV8TFTnkLtnCdqRkjJsre3GKRwHcZd3juE5jJHf under program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

Address:  8Sqz2zV8TFTnkLtnCdqRkjJsre3GKRwHcZd3juE5jJHf
Decimals:  9

Signature: 5wfYvovguPEbyv2uSWxGt9JcpTWgyuP4hY3wutjS32Ahnoni4qd7gf6sLre855WvT6xLHwrvV7J8bVmXymNU2qUz

$ spl-token create-account 8Sqz2zV8TFTnkLtnCdqRkjJsre3GKRwHcZd3juE5jJHf
Creating account 6XpKagP1N3K1XnzStufpV5YZ6DksEkQWgLNG9kPpLyvv

Signature: 2awxWdQMgv89ew34sEyG361vshB2wPXHHfva5iJ43dWr18f2Pr6awoXfsqYPpyS2eSbH6jhfVY9EUck8iJ4wCSN6

$ spl-token display 6XpKagP1N3K1XnzStufpV5YZ6DksEkQWgLNG9kPpLyvv
SPL Token Account
  Address: 6XpKagP1N3K1XnzStufpV5YZ6DksEkQWgLNG9kPpLyvv
  Program: TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
  Balance: 0
  Decimals: 9
  Mint: 8Sqz2zV8TFTnkLtnCdqRkjJsre3GKRwHcZd3juE5jJHf
  Owner: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
  State: Frozen
  Delegation: (not set)
  Close authority: (not set)
Extensions:
  Immutable owner
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
import {
    clusterApiUrl,
    sendAndConfirmTransaction,
    Connection,
    Keypair,
    SystemProgram,
    Transaction,
    LAMPORTS_PER_SOL,
} from '@solana/web3.js';
import {
    AccountState,
    createInitializeMintInstruction,
    createInitializeDefaultAccountStateInstruction,
    getMintLen,
    updateDefaultAccountState,
    ExtensionType,
    TOKEN_2022_PROGRAM_ID,
} from '@solana/spl-token';

(async () => {
    const payer = Keypair.generate();

    const mintAuthority = Keypair.generate();
    const freezeAuthority = Keypair.generate();
    const mintKeypair = Keypair.generate();
    const mint = mintKeypair.publicKey;

    const extensions = [ExtensionType.DefaultAccountState];
    const mintLen = getMintLen(extensions);
    const decimals = 9;

    const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');

    const airdropSignature = await connection.requestAirdrop(payer.publicKey, 2 * LAMPORTS_PER_SOL);
    await connection.confirmTransaction({ signature: airdropSignature, ...(await connection.getLatestBlockhash()) });

    const defaultState = AccountState.Frozen;

    const lamports = await connection.getMinimumBalanceForRentExemption(mintLen);
    const transaction = new Transaction().add(
        SystemProgram.createAccount({
            fromPubkey: payer.publicKey,
            newAccountPubkey: mint,
            space: mintLen,
            lamports,
            programId: TOKEN_2022_PROGRAM_ID,
        }),
        createInitializeDefaultAccountStateInstruction(mint, defaultState, TOKEN_2022_PROGRAM_ID),
        createInitializeMintInstruction(
            mint,
            decimals,
            mintAuthority.publicKey,
            freezeAuthority.publicKey,
            TOKEN_2022_PROGRAM_ID
        )
    );
    await sendAndConfirmTransaction(connection, transaction, [payer, mintKeypair], undefined);
})();
```

  </TabItem>
</Tabs>

#### Example: Updating default state

Over time, if the mint creator decides to relax this restriction, the freeze
authority may sign an `update_default_account_state` instruction to make all
accounts unfrozen by default.

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token update-default-account-state 8Sqz2zV8TFTnkLtnCdqRkjJsre3GKRwHcZd3juE5jJHf initialized

Signature: 3Mm2JCPrf6SrAe9awV3QzYvHiYmatiGWTmrQ7YnmzJSqyNCf75rLNMyH7jU26uZwX7q3MmBEBj1A36o5sGk9Vakb
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
    await updateDefaultAccountState(
        connection,
        payer,
        mint,
        AccountState.Initialized,
        freezeAuthority,
        [],
        undefined,
        TOKEN_2022_PROGRAM_ID
    );
```

  </TabItem>
</Tabs>

### Immutable Owner

Token account owners may reassign ownership to any other address. This is useful
in many situations, but it can also create security vulnerabilities.

For example, the addresses for Associated Token Accounts are derived based on
the owner and the mint, making it easy to find the "right" token account for an
owner. If the account owner has reassigned ownership of their associated token
account, then applications may derive the address for that account and use it,
not knowing that it does not belong to the owner anymore.

To avoid this issue, Token-2022 includes the `ImmutableOwner` extension, which
makes it impossible to reassign ownership of an account. The Associated Token
Account program always uses this extension when creating accounts.

#### Example: Explicitly creating an account with immutable ownership

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token
Creating token CZxztd7SEZWxg6B9PH5xa7QwKpMCpWBJiTLftw1o3qyV under program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

Address:  CZxztd7SEZWxg6B9PH5xa7QwKpMCpWBJiTLftw1o3qyV
Decimals:  9

Signature: 4fT19YaE3zAscj71n213K22M3wDSXgwSn39RBCVtiCTxMX7pZhAoHywP2QMKqWpZMB5vT7diQ8QaFp3abHztpyPC
$ solana-keygen new -o account.json
$ spl-token create-account CZxztd7SEZWxg6B9PH5xa7QwKpMCpWBJiTLftw1o3qyV account.json --immutable
Creating account EV2xsZto1TRqehewwWHUUQm68X6C6MepBSkbfZcVdShy

Signature: 5NqXiE3LPFnufnZhcwKPoZt7DaPR7qwfhmRr9W9ykhNM7rnu6MDdx7n5eTpEisiaSET2R4fZW7a91Ai6pCuskXF8
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
import {
    clusterApiUrl,
    sendAndConfirmTransaction,
    Connection,
    Keypair,
    SystemProgram,
    Transaction,
    LAMPORTS_PER_SOL,
} from '@solana/web3.js';
import {
    createAccount,
    createMint,
    createInitializeImmutableOwnerInstruction,
    createInitializeAccountInstruction,
    getAccountLen,
    ExtensionType,
    TOKEN_2022_PROGRAM_ID,
} from '@solana/spl-token';

(async () => {
    const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');

    const payer = Keypair.generate();
    const airdropSignature = await connection.requestAirdrop(payer.publicKey, 2 * LAMPORTS_PER_SOL);
    await connection.confirmTransaction({ signature: airdropSignature, ...(await connection.getLatestBlockhash()) });

    const mintAuthority = Keypair.generate();
    const decimals = 9;
    const mint = await createMint(
        connection,
        payer,
        mintAuthority.publicKey,
        mintAuthority.publicKey,
        decimals,
        undefined,
        undefined,
        TOKEN_2022_PROGRAM_ID
    );

    const accountLen = getAccountLen([ExtensionType.ImmutableOwner]);
    const lamports = await connection.getMinimumBalanceForRentExemption(accountLen);

    const owner = Keypair.generate();
    const accountKeypair = Keypair.generate();
    const account = accountKeypair.publicKey;
    const transaction = new Transaction().add(
        SystemProgram.createAccount({
            fromPubkey: payer.publicKey,
            newAccountPubkey: account,
            space: accountLen,
            lamports,
            programId: TOKEN_2022_PROGRAM_ID,
        }),
        createInitializeImmutableOwnerInstruction(account, TOKEN_2022_PROGRAM_ID),
        createInitializeAccountInstruction(account, mint, owner.publicKey, TOKEN_2022_PROGRAM_ID)
    );
    await sendAndConfirmTransaction(connection, transaction, [payer, accountKeypair], undefined);
})();
```

  </TabItem>
</Tabs>

#### Example: Creating an associated token account with immutable ownership

All associated token accounts have the immutable owner extension included, so
it's extremely easy to use the extension.

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token create-account CZxztd7SEZWxg6B9PH5xa7QwKpMCpWBJiTLftw1o3qyV
Creating account 4nvfLgYMERdNbbf1pADUSp44XukAyjeWWXCMkM1gMqC4

Signature: w4TRYDdCpTfmQh96E4UNgFFeiAHphWNaeYrJTu6bGyuPMokJrKFR33Ntj3iNQ5QQuFqom2CaYkhXiX9sBpWEW23
```

The CLI will tell us that it's unnecessary to specify the `--immutable` argument
if it's provided:

```console
$ spl-token create-account CZxztd7SEZWxg6B9PH5xa7QwKpMCpWBJiTLftw1o3qyV --immutable
Creating account 4nvfLgYMERdNbbf1pADUSp44XukAyjeWWXCMkM1gMqC4
Note: --immutable specified, but Token-2022 ATAs are always immutable, ignoring

Signature: w4TRYDdCpTfmQh96E4UNgFFeiAHphWNaeYrJTu6bGyuPMokJrKFR33Ntj3iNQ5QQuFqom2CaYkhXiX9sBpWEW23
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
    const associatedAccount = await createAccount(
        connection,
        payer,
        mint,
        owner.publicKey,
        undefined,
        undefined,
        TOKEN_2022_PROGRAM_ID
    );
```

  </TabItem>
</Tabs>

### Non-Transferable Tokens

To accompany immutably owned token accounts, the `NonTransferable` mint extension
allows for "soul-bound" tokens that cannot be moved to any other entity. For
example, this extension is perfect for achievements that can only belong to one
person or account.

This extension is very similar to issuing a token and then freezing the account,
but allows the owner to burn and close the account if they want.

#### Example: Creating a non-transferable mint

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --enable-non-transferable
Creating token 7De7wwkvNLPXpShbPDeRCLukb3CRzCNcC3iUuHtD6k4f under program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

Address:  7De7wwkvNLPXpShbPDeRCLukb3CRzCNcC3iUuHtD6k4f
Decimals:  9

Signature: 2QtCBwCo2J9hf2Prd2t4CBBUxEXQCBSSD5gkNc59AwhxsKgRp92czNAvwWDxjeXGFCWSuNmzAcD19cEpqubovDDv
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
import {
    clusterApiUrl,
    sendAndConfirmTransaction,
    Connection,
    Keypair,
    SystemProgram,
    Transaction,
    LAMPORTS_PER_SOL,
} from '@solana/web3.js';
import {
    createInitializeNonTransferableMintInstruction,
    createInitializeMintInstruction,
    getMintLen,
    ExtensionType,
    TOKEN_2022_PROGRAM_ID,
} from '@solana/spl-token';

(async () => {
    const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');

    const payer = Keypair.generate();
    const airdropSignature = await connection.requestAirdrop(payer.publicKey, 2 * LAMPORTS_PER_SOL);
    await connection.confirmTransaction({ signature: airdropSignature, ...(await connection.getLatestBlockhash()) });

    const mintAuthority = Keypair.generate();
    const decimals = 9;

    const mintKeypair = Keypair.generate();
    const mint = mintKeypair.publicKey;
    const mintLen = getMintLen([ExtensionType.NonTransferable]);
    const lamports = await connection.getMinimumBalanceForRentExemption(mintLen);

    const transaction = new Transaction().add(
        SystemProgram.createAccount({
            fromPubkey: payer.publicKey,
            newAccountPubkey: mint,
            space: mintLen,
            lamports,
            programId: TOKEN_2022_PROGRAM_ID,
        }),
        createInitializeNonTransferableMintInstruction(mint, TOKEN_2022_PROGRAM_ID),
        createInitializeMintInstruction(mint, decimals, mintAuthority.publicKey, null, TOKEN_2022_PROGRAM_ID)
    );
    await sendAndConfirmTransaction(connection, transaction, [payer, mintKeypair], undefined);
})();
```

  </TabItem>
</Tabs>

### Required Memo on Transfer

Traditional banking systems typically require a memo to accompany all transfers.
The Token-2022 program contains an extension to satisfy this requirement.

By enabling required memo transfers on your token account, the program enforces
that all incoming transfers must have an accompanying memo instruction right
before the transfer instruction.

**Note**: This also works in CPI contexts, as long as a CPI is performed to log
the memo before invoking the transfer.

#### Example: Create account with required memo transfers

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token
Creating token EbPBt3XkCb9trcV4c8fidhrvoeURbDbW87Acustzyi8N under program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

Address:  EbPBt3XkCb9trcV4c8fidhrvoeURbDbW87Acustzyi8N
Decimals:  9

Signature: 2mCoV3ujSUArgZMyayiYtLZp2QzpqKx3NXnv9W8DpinY39rBU2yGmYLfp2tZ9uZqVbfJ6Mf3SqDHexdCcFcDAEvc
$ spl-token create-account EbPBt3XkCb9trcV4c8fidhrvoeURbDbW87Acustzyi8N
Creating account 4Uzz67txwYbfYpF8r5UGEMYJwhPAYQ5eFUY89KTYc2bL

Signature: 57wZHDaQtSzszDkusrnozZNj5PemQhpqHMEFLWFKpqASCErcDuBuYuEky5g3evHtkjMrKgh1s3aEap1L8y5UhW5W
$ spl-token enable-required-transfer-memos 4Uzz67txwYbfYpF8r5UGEMYJwhPAYQ5eFUY89KTYc2bL
Signature: 5MnWtrhMK32zkbacDMwBNft48VAUpr4EoRM87hkT9AFYvPgPEU7V7ERV6gdfb3kASri4wnUnr13hNKuYJ66pD8Fs
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
import {
    clusterApiUrl,
    sendAndConfirmTransaction,
    Connection,
    Keypair,
    SystemProgram,
    Transaction,
    LAMPORTS_PER_SOL,
} from '@solana/web3.js';
import { createMemoInstruction } from '@solana/spl-memo';
import {
    createAssociatedTokenAccount,
    createMint,
    createEnableRequiredMemoTransfersInstruction,
    createInitializeAccountInstruction,
    createTransferInstruction,
    disableRequiredMemoTransfers,
    enableRequiredMemoTransfers,
    getAccountLen,
    mintTo,
    ExtensionType,
    TOKEN_2022_PROGRAM_ID,
} from '@solana/spl-token';

(async () => {
    const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');

    const payer = Keypair.generate();
    const airdropSignature = await connection.requestAirdrop(payer.publicKey, 2 * LAMPORTS_PER_SOL);
    await connection.confirmTransaction({ signature: airdropSignature, ...(await connection.getLatestBlockhash()) });

    const mintAuthority = Keypair.generate();
    const decimals = 9;
    const mint = await createMint(
        connection,
        payer,
        mintAuthority.publicKey,
        mintAuthority.publicKey,
        decimals,
        undefined,
        undefined,
        TOKEN_2022_PROGRAM_ID
    );

    const accountLen = getAccountLen([ExtensionType.MemoTransfer]);
    const lamports = await connection.getMinimumBalanceForRentExemption(accountLen);

    const owner = Keypair.generate();
    const destinationKeypair = Keypair.generate();
    const destination = destinationKeypair.publicKey;
    const transaction = new Transaction().add(
        SystemProgram.createAccount({
            fromPubkey: payer.publicKey,
            newAccountPubkey: destination,
            space: accountLen,
            lamports,
            programId: TOKEN_2022_PROGRAM_ID,
        }),
        createInitializeAccountInstruction(destination, mint, owner.publicKey, TOKEN_2022_PROGRAM_ID),
        createEnableRequiredMemoTransfersInstruction(destination, owner.publicKey, [], TOKEN_2022_PROGRAM_ID)
    );

    await sendAndConfirmTransaction(connection, transaction, [payer, owner, destinationKeypair], undefined);

})();
```

  </TabItem>
</Tabs>

#### Example: Enabling or disabling required memo transfers

An account owner may always choose to flip required memo transfers on or off.

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token disable-required-transfer-memos 4Uzz67txwYbfYpF8r5UGEMYJwhPAYQ5eFUY89KTYc2bL
Signature: 5a9X8JrWzwZqb3iMonfUfSZbisQ57aEmW5cFntWGYRv2UZx8ACkMineBEQRHwLMzYHeyFDEHMXu8zqAMv5tm4u1g

$ spl-token enable-required-transfer-memos 4Uzz67txwYbfYpF8r5UGEMYJwhPAYQ5eFUY89KTYc2bL
Signature: 5MnWtrhMK32zkbacDMwBNft48VAUpr4EoRM87hkT9AFYvPgPEU7V7ERV6gdfb3kASri4wnUnr13hNKuYJ66pD8Fs
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
    await disableRequiredMemoTransfers(connection, payer, destination, owner, [], undefined, TOKEN_2022_PROGRAM_ID);

    await enableRequiredMemoTransfers(connection, payer, destination, owner, [], undefined, TOKEN_2022_PROGRAM_ID);
```

  </TabItem>
</Tabs>

#### Example: Transferring with a memo

When transferring into an account with required transfer memos, you must include
a memo instruction before the transfer.

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token transfer EbPBt3XkCb9trcV4c8fidhrvoeURbDbW87Acustzyi8N 10 4Uzz67txwYbfYpF8r5UGEMYJwhPAYQ5eFUY89KTYc2bL --with-memo "memo text"
Signature: 5a9X8JrWzwZqb3iMonfUfSZbisQ57aEmW5cFntWGYRv2UZx8ACkMineBEQRHwLMzYHeyFDEHMXu8zqAMv5tm4u1g
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
    const sourceTokenAccount = await createAssociatedTokenAccount(
        connection,
        payer,
        mint,
        payer.publicKey,
        undefined,
        TOKEN_2022_PROGRAM_ID
    );
    await mintTo(connection, payer, mint, sourceTokenAccount, mintAuthority, 100, [], undefined, TOKEN_2022_PROGRAM_ID);

    const transferTransaction = new Transaction().add(
        createMemoInstruction('Hello, memo-transfer!', [payer.publicKey]),
        createTransferInstruction(sourceTokenAccount, destination, payer.publicKey, 100, [], TOKEN_2022_PROGRAM_ID)
    );
    await sendAndConfirmTransaction(connection, transferTransaction, [payer], undefined);
```

  </TabItem>
</Tabs>

### Reallocate

In the previous example, astute readers of the JavaScript code may have noticed
that the `EnableRequiredMemoTransfers` instruction came after `InitializeAccount`,
which means that this extension can be enabled after the account is already
created.

In order to actually add this extension after the account is created, however,
you may need to reallocate more space in the account for the additional extension
bytes.

The `Reallocate` instruction allows an owner to reallocate their token account
to fit room for more extensions.

#### Example: Reallocating existing account to enable required memo transfers

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

The CLI reallocs automatically, so if you use `enable-required-transfer-memos`
with an account that does not have enough space, it will add the `Reallocate`
instruction.

```console
$ spl-token create-account EbPBt3XkCb9trcV4c8fidhrvoeURbDbW87Acustzyi8N
Creating account 4Uzz67txwYbfYpF8r5UGEMYJwhPAYQ5eFUY89KTYc2bL

Signature: 57wZHDaQtSzszDkusrnozZNj5PemQhpqHMEFLWFKpqASCErcDuBuYuEky5g3evHtkjMrKgh1s3aEap1L8y5UhW5W
$ spl-token enable-required-transfer-memos 4Uzz67txwYbfYpF8r5UGEMYJwhPAYQ5eFUY89KTYc2bL
Signature: 5MnWtrhMK32zkbacDMwBNft48VAUpr4EoRM87hkT9AFYvPgPEU7V7ERV6gdfb3kASri4wnUnr13hNKuYJ66pD8Fs
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
import {
    clusterApiUrl,
    sendAndConfirmTransaction,
    Connection,
    Keypair,
    Transaction,
    LAMPORTS_PER_SOL,
} from '@solana/web3.js';
import {
    createAccount,
    createMint,
    createEnableRequiredMemoTransfersInstruction,
    createReallocateInstruction,
    ExtensionType,
    TOKEN_2022_PROGRAM_ID,
} from '@solana/spl-token';

(async () => {
    const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');

    const payer = Keypair.generate();
    const airdropSignature = await connection.requestAirdrop(payer.publicKey, 2 * LAMPORTS_PER_SOL);
    await connection.confirmTransaction({ signature: airdropSignature, ...(await connection.getLatestBlockhash()) });

    const mintAuthority = Keypair.generate();
    const decimals = 9;
    const mint = await createMint(
        connection,
        payer,
        mintAuthority.publicKey,
        mintAuthority.publicKey,
        decimals,
        undefined,
        undefined,
        TOKEN_2022_PROGRAM_ID
    );

    const owner = Keypair.generate();
    const account = await createAccount(
        connection,
        payer,
        mint,
        owner.publicKey,
        undefined,
        undefined,
        TOKEN_2022_PROGRAM_ID
    );

    const extensions = [ExtensionType.MemoTransfer];
    const transaction = new Transaction().add(
        createReallocateInstruction(
            account,
            payer.publicKey,
            extensions,
            owner.publicKey,
            undefined,
            TOKEN_2022_PROGRAM_ID
        ),
        createEnableRequiredMemoTransfersInstruction(account, owner.publicKey, [], TOKEN_2022_PROGRAM_ID)
    );
    await sendAndConfirmTransaction(connection, transaction, [payer, owner], undefined);
})();
```

  </TabItem>
</Tabs>

### Interest-Bearing Tokens

Tokens that constantly grow or decrease in value have many uses in the real world.
The most well known example is a bond.

With Token, this has only been possible through proxy contracts that require
regular rebase or update operations.

With the Token-2022 extension model, however, we have the possibility to change
how the UI amount of tokens are represented. Using the `InterestBearingMint`
extension and the `amount_to_ui_amount` instruction, you can set an interest
rate on your token and fetch its amount with interest at any time.

Interest is continuously compounded based on the timestamp in the network. Due
to drift that may occur in the network timestamp, the accumulated interest could
be lower than the expected value. Thankfully, this is rare.

**Note**: No new tokens are ever created, the UI amount returns the amount of tokens
plus all interest the tokens have accumulated. The feature is entirely cosmetic.

#### Example: Create an interest-bearing mint

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --interest-rate 10
Creating token 7N4HggYEJAtCLJdnHGCtFqfxcB5rhQCsQTze3ftYstVj under program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

Address:  7N4HggYEJAtCLJdnHGCtFqfxcB5rhQCsQTze3ftYstVj
Decimals:  9

Signature: 5dSW5QUacEsaKYb3MwYp4ycqq4jpNJ1rpLhS5rotoe3CWv9XhhjrncUFpk14R1fRamS1xprziC3NkpbYno4c8JxD
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
import { clusterApiUrl, Connection, Keypair, LAMPORTS_PER_SOL } from '@solana/web3.js';
import { createInterestBearingMint, updateRateInterestBearingMint, TOKEN_2022_PROGRAM_ID } from '@solana/spl-token';

(async () => {
    const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');

    const payer = Keypair.generate();
    const airdropSignature = await connection.requestAirdrop(payer.publicKey, 2 * LAMPORTS_PER_SOL);
    await connection.confirmTransaction({ signature: airdropSignature, ...(await connection.getLatestBlockhash()) });

    const mintAuthority = Keypair.generate();
    const freezeAuthority = Keypair.generate();
    const rateAuthority = Keypair.generate();
    const mintKeypair = Keypair.generate();
    const rate = 10;
    const decimals = 9;
    const mint = await createInterestBearingMint(
        connection,
        payer,
        mintAuthority.publicKey,
        freezeAuthority.publicKey,
        rateAuthority.publicKey,
        rate,
        decimals,
        mintKeypair,
        undefined,
        TOKEN_2022_PROGRAM_ID
    );
})();
```

  </TabItem>
</Tabs>

#### Example: Update the interest rate

The rate authority may update the interest rate on the mint at any time.

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token set-interest-rate 7N4HggYEJAtCLJdnHGCtFqfxcB5rhQCsQTze3ftYstVj 50
Setting Interest Rate for 7N4HggYEJAtCLJdnHGCtFqfxcB5rhQCsQTze3ftYstVj to 50 bps

Signature: 5DQs6hzkfGq3uotESuVwF7MGeMawwfQcm1e9RHaUeVySDV6xpUzYhzdb6ygqJfsEZqewgiDR5KuxaGzkdTMcDrTn
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
    const updateRate = 50;
    await updateRateInterestBearingMint(
        connection,
        payer,
        mint,
        rateAuthority,
        updateRate,
        [],
        undefined,
        TOKEN_2022_PROGRAM_ID
    );
```

  </TabItem>
</Tabs>

### Permanent Delegate

With Token-2022, it's possible to specify a permanent account delegate for a
mint. This authority has unlimited delegate privileges over any account for that
mint, meaning that it can burn or transfer any amount of tokens.

While this feature certainly has room for abuse, it has many important real-world
use cases.

In some jurisdictions, a stablecoin issuer must be able to seize assets from
sanctioned entities. Through the permanent delegate, the stablecoin issuer can
transfer or burn tokens from accounts owned by sanctioned entities.

It's also possible to implement a [Harberger Tax](http://www.harbergertax.com/)
on an NFT, whereby an auction program has permanent delegate authority for the
token. After a sale, the permanent delegate can move the NFT from the owner to
the buyer if the previous owner doesn't pay the tax.

#### Example: Create a mint with a permanent delegate

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --enable-permanent-delegate
Creating token 7LUgoQCqhk3VMPhpAnmS1zdCFW4C6cupxgbqWrTwydGx under program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

Address:  7LUgoQCqhk3VMPhpAnmS1zdCFW4C6cupxgbqWrTwydGx
Decimals:  9

Signature: 439yVq2WfUEegAPv5BAkFampBPo696UbZ58RAYCzvUcbcBcxhfThpt1pcdKmiQrurHj65CqmWiHzrfT12BhL3Nxb
```

The CLI defaults the permanent delegate to the mint authority, but you can change
it using the `authorize` command.

```console
$ spl-token authorize 7LUgoQCqhk3VMPhpAnmS1zdCFW4C6cupxgbqWrTwydGx permanent-delegate GFMniFoE5X4F87L9jzjHaW4MTkXyX1AYHNfhFencgamg
Updating 7LUgoQCqhk3VMPhpAnmS1zdCFW4C6cupxgbqWrTwydGx 
  Current permanent delegate: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
  New permanent delegate: GFMniFoE5X4F87L9jzjHaW4MTkXyX1AYHNfhFencgamg

Signature: 2ABDrR6meXk4rrAwd2LsHaTsnM5BuTC9RbiZmgBxgzze8ZM2yxuYp8iyg8viHgVaKRbXGzjKsFjF5RR9Kkzn4Prj
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
import {
    clusterApiUrl,
    sendAndConfirmTransaction,
    Connection,
    Keypair,
    SystemProgram,
    Transaction,
    LAMPORTS_PER_SOL,
} from '@solana/web3.js';

import {
    ExtensionType,
    createInitializeMintInstruction,
    createInitializePermanentDelegateInstruction,
    mintTo,
    createAccount,
    getMintLen,
    TOKEN_2022_PROGRAM_ID,
} from '@solana/spl-token';

(async () => {
    const payer = Keypair.generate();

    const mintAuthority = Keypair.generate();
    const mintKeypair = Keypair.generate();
    const mint = mintKeypair.publicKey;
    const permanentDelegate = Keypair.generate();

    const extensions = [ExtensionType.PermanentDelegate];
    const mintLen = getMintLen(extensions);
    const decimals = 9;

    const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');

    const airdropSignature = await connection.requestAirdrop(payer.publicKey, 2 * LAMPORTS_PER_SOL);
    await connection.confirmTransaction({ signature: airdropSignature, ...(await connection.getLatestBlockhash()) });

    const mintLamports = await connection.getMinimumBalanceForRentExemption(mintLen);
    const mintTransaction = new Transaction().add(
        SystemProgram.createAccount({
            fromPubkey: payer.publicKey,
            newAccountPubkey: mint,
            space: mintLen,
            lamports: mintLamports,
            programId: TOKEN_2022_PROGRAM_ID,
        }),
        createInitializePermanentDelegateInstruction(mint, permanentDelegate.publicKey, TOKEN_2022_PROGRAM_ID),
        createInitializeMintInstruction(mint, decimals, mintAuthority.publicKey, null, TOKEN_2022_PROGRAM_ID)
    );
    await sendAndConfirmTransaction(connection, mintTransaction, [payer, mintKeypair], undefined);
})();
```

  </TabItem>
</Tabs>

### CPI Guard

CPI Guard is an extension that prohibits certain actions inside cross-program
invocations, to protect users from implicitly signing for actions they can't see,
hidden in programs that aren't the System or Token programs.

Users may choose to enable or disable the CPI Guard extension on their token
account at will. When enabled, it has the following effects during CPI:

* Transfer: the signing authority must be the account delegate
* Burn: the signing authority must be the account delegate
* Approve: prohibited
* Close Account: the lamport destination must be the account owner
* Set Close Authority: prohibited unless unsetting
* Set Owner: always prohibited, including outside CPI

#### Background

When interacting with a dapp, users sign transactions that are constructed by
frontend code. Given a user's signature, there are three fundamental ways for a
dapp to transfer funds from the user to the dapp (or, equivalently, burn them):

* Insert a transfer instruction in the transaction
* Insert an approve instruction in the transaction, and perform a CPI transfer
under program authority
* Insert an opaque program instruction, and perform a CPI transfer with the user's
authorization

The first two are safe, in that the user can see exactly what is being done, with
zero ambiguity. The third is quite dangerous. A wallet signature allows the program
to perform any action as the user, without any visibility into its actions. There
have been some attempts at workarounds, for instance, simulating the transaction
and warning about balance changes. But, fundamentally, this is intractable.

There are two ways to make this much safer:

* Wallets warn whenever a wallet signature is made available to an opaque
(non-system, non-token) instruction. Users should be educated to treat the request
for a signature on such an instruction as highly suspect
* The token program prohibits CPI calls with the user authority, forcing opaque
programs to directly ask for the user's authority

The CPI Guard covers the second instance.

#### Example: Enable CPI Guard on a token account

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>
```console
$ spl-token enable-cpi-guard 4YfkXX89TrsWqSSxb3av36Rk8EZBoDqxGzuaDNXr7UnL

Signature: 2fohon7oraTCgBZB3dfzhpGsBobYmYPgA8nvgCqKzjqpdX6EYZaBY3VwzjNuwDpsFYYNbpTVYBjxqiaMBrvXM8S2
```
  </TabItem>
  <TabItem value="jsx" label="JS">
```jsx
import {
    clusterApiUrl,
    sendAndConfirmTransaction,
    Connection,
    Keypair,
    SystemProgram,
    Transaction,
    LAMPORTS_PER_SOL,
} from '@solana/web3.js';
import {
    createMint,
    createEnableCpiGuardInstruction,
    createInitializeAccountInstruction,
    disableCpiGuard,
    enableCpiGuard,
    getAccountLen,
    ExtensionType,
    TOKEN_2022_PROGRAM_ID,
} from '@solana/spl-token';

(async () => {
    const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');

    const payer = Keypair.generate();
    const airdropSignature = await connection.requestAirdrop(payer.publicKey, 2 * LAMPORTS_PER_SOL);
    await connection.confirmTransaction({ signature: airdropSignature, ...(await connection.getLatestBlockhash()) });

    const mintAuthority = Keypair.generate();
    const decimals = 9;
    const mint = await createMint(
        connection,
        payer,
        mintAuthority.publicKey,
        mintAuthority.publicKey,
        decimals,
        undefined,
        undefined,
        TOKEN_2022_PROGRAM_ID
    );

    const accountLen = getAccountLen([ExtensionType.CpiGuard]);
    const lamports = await connection.getMinimumBalanceForRentExemption(accountLen);

    const owner = Keypair.generate();
    const destinationKeypair = Keypair.generate();
    const destination = destinationKeypair.publicKey;
    const transaction = new Transaction().add(
        SystemProgram.createAccount({
            fromPubkey: payer.publicKey,
            newAccountPubkey: destination,
            space: accountLen,
            lamports,
            programId: TOKEN_2022_PROGRAM_ID,
        }),
        createInitializeAccountInstruction(destination, mint, owner.publicKey, TOKEN_2022_PROGRAM_ID),
        createEnableCpiGuardInstruction(destination, owner.publicKey, [], TOKEN_2022_PROGRAM_ID)
    );

    await sendAndConfirmTransaction(connection, transaction, [payer, owner, destinationKeypair], undefined);

    // OR
    await enableCpiGuard(connection, payer, destination, owner, [], undefined, TOKEN_2022_PROGRAM_ID);
})();
```
  </TabItem>
</Tabs>

#### Example: Disable CPI Guard on a token account

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>
```console
$ spl-token disable-cpi-guard 4YfkXX89TrsWqSSxb3av36Rk8EZBoDqxGzuaDNXr7UnL

Signature: 4JJSBSc1UAtArbBqYRpTk9264WwJuZ8n6NqyXtCSmyVQpmHoetzyVDwHxtxrdK8wQawoocDxFD9rRPhpAMzJ6EdG
```
  </TabItem>
  <TabItem value="jsx" label="JS">
```jsx
    await disableCpiGuard(connection, payer, destination, owner, [], undefined, TOKEN_2022_PROGRAM_ID);
```
  </TabItem>
</Tabs>

### Transfer Hook

#### Motivation

Token creators may need more control over how their token is transferred. The
most prominent use case revolves around NFT royalties. Whenever a token is moved,
the creator should be entitled to royalties, but due to the design of the current
token program, it's impossible to stop a transfer at the protocol level.

Current solutions typically resort to perpetually freezing tokens, which requires
a whole proxy layer to interact with the token. Wallets and marketplaces need
to be aware of the proxy layer in order to properly use the token.

Worse still, different royalty systems have different proxy layers for using
their token. All in all, these systems harm composability and make development
harder.

#### Solution

To improve the situation, Token-2022 introduces the concept of the
[transfer-hook interface](../transfer-hook-interface)
and extension. A token creator must develop and deploy a program that
implements the interface and then configure their token mint to use their program.

During transfer, Token-2022 calls into the program with the accounts specified
at a well-defined program-derived address for that mint and program id. This
call happens after all other transfer logic, so the accounts reflect the *end*
state of the transfer.

When interacting with a transfer-hook program, it's possible to send an
instruction - such as `Execute` (transfer) - to the program with only the
accounts required for the `Transfer` instruction, and any extra accounts that
the program may require are automatically resolved on-chain! This process is
explained in detail in many of the linked `README` files below under
**Resources**.

#### Resources

The interface description and structs exist at
[spl-transfer-hook-interface](https://github.com/solana-labs/solana-program-library/tree/master/token/transfer-hook/interface),
along with a sample minimal program implementation. You can find detailed
instructions on how to implement this interface for an on-chain program or
interact with a program that implements transfer-hook in the repository's
[README](https://github.com/solana-labs/solana-program-library/tree/master/token/transfer-hook/interface/README.md).

The `spl-transfer-hook-interface` library provides offchain and onchain helpers
for resolving the additional accounts required. See
[onchain.rs](https://github.com/solana-labs/solana-program-library/blob/master/token/transfer-hook/interface/src/onchain.rs)
for usage on-chain, and
[offchain.rs](https://github.com/solana-labs/solana-program-library/tree/master/token/transfer-hook/interface/src/offchain.rs)
for fetching the additional required account metas with any async off-chain client
like `BanksClient` or `RpcClient`.

A usable example program exists at
[spl-transfer-hook-example](https://github.com/solana-labs/solana-program-library/tree/master/token/transfer-hook/example).
Token-2022 uses this example program in tests to ensure that it properly uses
the transfer hook interface.

The example program and the interface are powered by the
[spl-tlv-account-resolution](https://github.com/solana-labs/solana-program-library/tree/master/libraries/tlv-account-resolution)
library, which is explained in detail in the repository's 
[README](https://github.com/solana-labs/solana-program-library/tree/master/libraries/tlv-account-resolution/README.md)

#### Example: Create a mint with a transfer hook

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --transfer-hook 7N4HggYEJAtCLJdnHGCtFqfxcB5rhQCsQTze3ftYstVj
Creating token HFg1FFaj4PqFHmkYrqbZsarNJEZT436aXAXgQFMJihwc under program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

Address:  HFg1FFaj4PqFHmkYrqbZsarNJEZT436aXAXgQFMJihwc
Decimals:  9

Signature: 3ug4Ejs16jJgEm1WyBwDDxzh9xqPzQ3a2cmy1hSYiPFcLQi9U12HYF1Dbhzb2bx75SSydfU6W4e11dGUXaPbJqVc
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```jsx
import {
    clusterApiUrl,
    sendAndConfirmTransaction,
    Connection,
    Keypair,
    PublicKey,
    SystemProgram,
    Transaction,
    LAMPORTS_PER_SOL,
} from '@solana/web3.js';

import {
    ExtensionType,
    createInitializeMintInstruction,
    createInitializeTransferHookInstruction,
    mintTo,
    createAccount,
    getMintLen,
    TOKEN_2022_PROGRAM_ID,
} from '@solana/spl-token';

(async () => {
    const payer = Keypair.generate();

    const mintAuthority = Keypair.generate();
    const mintKeypair = Keypair.generate();
    const mint = mintKeypair.publicKey;

    const extensions = [ExtensionType.TransferHook];
    const mintLen = getMintLen(extensions);
    const decimals = 9;
    const transferHookProgramId = new PublicKey('7N4HggYEJAtCLJdnHGCtFqfxcB5rhQCsQTze3ftYstVj')

    const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');

    const airdropSignature = await connection.requestAirdrop(payer.publicKey, 2 * LAMPORTS_PER_SOL);
    await connection.confirmTransaction({ signature: airdropSignature, ...(await connection.getLatestBlockhash()) });

    const mintLamports = await connection.getMinimumBalanceForRentExemption(mintLen);
    const mintTransaction = new Transaction().add(
        SystemProgram.createAccount({
            fromPubkey: payer.publicKey,
            newAccountPubkey: mint,
            space: mintLen,
            lamports: mintLamports,
            programId: TOKEN_2022_PROGRAM_ID,
        }),
        createInitializeTransferHookInstruction(mint, payer.publicKey, transferHookProgramId, TOKEN_2022_PROGRAM_ID),
        createInitializeMintInstruction(mint, decimals, mintAuthority.publicKey, null, TOKEN_2022_PROGRAM_ID)
    );
    await sendAndConfirmTransaction(connection, mintTransaction, [payer, mintKeypair], undefined);
})();
```

  </TabItem>
</Tabs>

#### Example: Update transfer-hook program in mint

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token set-transfer-hook HFg1FFaj4PqFHmkYrqbZsarNJEZT436aXAXgQFMJihwc EbPBt3XkCb9trcV4c8fidhrvoeURbDbW87Acustzyi8N

Signature: 3Ffw6yjseDsL3Az5n2LjdwXXwVPYxDF3JUU1JC1KGAEb1LE68S9VN4ebtAyvKeYMHvhjdz1LJVyugGNdWHyotzay
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```js
await updateTransferHook(
  connection,
  payer, mint,
  newTransferHookProgramId,
  payer.publicKey,
  [],
  undefined,
  TOKEN_2022_PROGRAM_ID
);
```

  </TabItem>
</Tabs>

#### Example: Manage a transfer-hook program

A sample CLI for managing a transfer-hook program exists at
[spl-transfer-hook-cli](https://github.com/solana-labs/solana-program-library/tree/master/token/transfer-hook/cli).
A mint manager can fork the tool for their own program.

It only contains a command to create the required transfer-hook account for the
mint.

First, you must build the transfer-hook program and deploy it:

```console
$ cargo build-sbf
$ solana program deploy target/deploy/spl-transfer-hook-example.so
```

After that, you can initialize the transfer-hook account:

```console
$ spl-transfer-hook create-extra-metas <PROGRAM_ID> <MINT_ID> [<ACCOUNT_PUBKEY>:<ROLE> ...]
```

### Metadata Pointer

With the potential proliferation of multiple metadata programs, a mint can have
multiple different accounts all claiming to describe the mint.

To make it easy for clients to distinguish, the metadata pointer extension allows
a token creator to designate an address that describes the canonical metadata.
As you'll see in the "Metadata" section, this address can be the mint itself!

To avoid phony mints claiming to be stablecoins, however, a client must check
that the mint and the metadata both point to each other.

#### Example: Create a mint with a metadata pointer to an external account

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --metadata-address 7N4HggYEJAtCLJdnHGCtFqfxcB5rhQCsQTze3ftYstVj
Creating token HFg1FFaj4PqFHmkYrqbZsarNJEZT436aXAXgQFMJihwc under program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

Address:  HFg1FFaj4PqFHmkYrqbZsarNJEZT436aXAXgQFMJihwc
Decimals:  9

Signature: 3ug4Ejs16jJgEm1WyBwDDxzh9xqPzQ3a2cmy1hSYiPFcLQi9U12HYF1Dbhzb2bx75SSydfU6W4e11dGUXaPbJqVc
```

  </TabItem>
  <TabItem value="jsx" label="JS">

Coming soon!

  </TabItem>
</Tabs>

### Metadata

To facilitate token-metadata usage, Token-2022 allows a mint creator to include
their token's metadata directly in the mint account.

Token-2022 implements all of the instructions from the
[spl-token-metadata-interface](https://github.com/solana-labs/solana-program-library/tree/master/token-metadata/interface).

The metadata extension should work directly with the metadata-pointer extension.
During mint creation, you should also add the metadata-pointer extension, pointed
at the mint itself.

The tools do this for you automatically.

#### Example: Create a mint with metadata

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --enable-metadata
Creating token 5K8RVdjpY3CHujyKjQ7RkyiCJqTG8Kba9krNfpZnmvpS under program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
To initialize metadata inside the mint, please run `spl-token initialize-metadata 5K8RVdjpY3CHujyKjQ7RkyiCJqTG8Kba9krNfpZnmvpS <YOUR_TOKEN_NAME> <YOUR_TOKEN_SYMBOL> <YOUR_TOKEN_URI>`, and sign with the mint authority

Address:  5K8RVdjpY3CHujyKjQ7RkyiCJqTG8Kba9krNfpZnmvpS
Decimals:  9

Signature: 2BZH8KE7zVcBj7Mmnu6uCM9NT4ey7qHasZmEk6Bt3tyx1wKCXS3JtcgEvrXXEMFB5numQgA9wvR67o2Z4YQdEw7m

$ spl-token initialize-metadata 5K8RVdjpY3CHujyKjQ7RkyiCJqTG8Kba9krNfpZnmvpS MyTokenName TOKEN http://my.token --update-authority 3pGiHDDek35npQuyWQ7FGcWxqJdHvVPDHDDmBFs2YxQj
Signature: 2H16XtBqdwSbvvq8g5o2jhy4TknP6zgt71KHawEdyPvNuvusQrV4dPccUrMqjFeNTbk75AtzmzUVueH3yWiTjBCG
```

  </TabItem>
  <TabItem value="jsx" label="JS">

```js
import {
  clusterApiUrl,
  Connection,
  Keypair,
  LAMPORTS_PER_SOL,
  sendAndConfirmTransaction,
  SystemProgram,
  Transaction,
} from '@solana/web3.js';
import {
  createInitializeMetadataPointerInstruction,
  createInitializeMintInstruction,
  ExtensionType,
  getMintLen,
  LENGTH_SIZE,
  TOKEN_2022_PROGRAM_ID,
  TYPE_SIZE,
} from '@solana/spl-token';
import { createInitializeInstruction, pack, TokenMetadata } from '@solana/spl-token-metadata';

(async () => {
  const payer = Keypair.generate();

  const mint = Keypair.generate();
  const decimals = 9;

  const metadata: TokenMetadata = {
    mint: mint.publicKey,
    name: 'TOKEN_NAME',
    symbol: 'SMBL',
    uri: 'URI',
    additionalMetadata: [['new-field', 'new-value']],
  };

  const mintLen = getMintLen([ExtensionType.MetadataPointer]);

  const metadataLen = TYPE_SIZE + LENGTH_SIZE + pack(metadata).length;

  const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');

  const airdropSignature = await connection.requestAirdrop(payer.publicKey, 2 * LAMPORTS_PER_SOL);
  await connection.confirmTransaction({
    signature: airdropSignature,
    ...(await connection.getLatestBlockhash()),
  });

  const mintLamports = await connection.getMinimumBalanceForRentExemption(mintLen + metadataLen);
  const mintTransaction = new Transaction().add(
    SystemProgram.createAccount({
      fromPubkey: payer.publicKey,
      newAccountPubkey: mint.publicKey,
      space: mintLen,
      lamports: mintLamports,
      programId: TOKEN_2022_PROGRAM_ID,
    }),
    createInitializeMetadataPointerInstruction(mint.publicKey, payer.publicKey, mint.publicKey, TOKEN_2022_PROGRAM_ID),
    createInitializeMintInstruction(mint.publicKey, decimals, payer.publicKey, null, TOKEN_2022_PROGRAM_ID),
    createInitializeInstruction({
      programId: TOKEN_2022_PROGRAM_ID,
      mint: mint.publicKey,
      metadata: mint.publicKey,
      name: metadata.name,
      symbol: metadata.symbol,
      uri: metadata.uri,
      mintAuthority: payer.publicKey,
      updateAuthority: payer.publicKey,
    }),
  );
  await sendAndConfirmTransaction(connection, mintTransaction, [payer, mint]);
})();
```

  </TabItem>
</Tabs>

#### Example: Update a field

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token update-metadata 5K8RVdjpY3CHujyKjQ7RkyiCJqTG8Kba9krNfpZnmvpS name YourToken
Signature: 2H16XtBqdwSbvvq8g5o2jhy4TknP6zgt71KHawEdyPvNuvusQrV4dPccUrMqjFeNTbk75AtzmzUVueH3yWiTjBCG
```
  </TabItem>
  <TabItem value="jsx" label="JS">

```js
import { createUpdateFieldInstruction } from "@solana/spl-token-metadata";

(async () => {
  const tx = new Transaction().add(
    createUpdateFieldInstruction({
      metadata: mint.publicKey,
      updateAuthority: payer.publicKey,
      programId: TOKEN_2022_PROGRAM_ID,
      field: 'name',
      value: 'YourToken',
    }),
  );
  await sendAndConfirmTransaction(connection, tx, [ payer, mint ]);
})();
```

  </TabItem>
</Tabs>

#### Example: Add a custom field

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token update-metadata 5K8RVdjpY3CHujyKjQ7RkyiCJqTG8Kba9krNfpZnmvpS new-field new-value
Signature: 31uerYNa6yhb21k5CCX69k7RLUKEhJEV99UadEpPnZtWWpykwr7vkTFkuFeJ7AaEyQPrepe8m8xr4N23JEAeuTRY
```
  </TabItem>
  <TabItem value="jsx" label="JS">

```js
import { createUpdateFieldInstruction } from "@solana/spl-token-metadata";

(async () => {
  const tx = new Transaction().add(
    createUpdateFieldInstruction({
      metadata: mint.publicKey,
      updateAuthority: payer.publicKey,
      programId: TOKEN_2022_PROGRAM_ID,
      field: 'new-field',
      value: 'new-value',
    }),
  );
  await sendAndConfirmTransaction(connection, tx, [ payer, mint ]);
})();
```

  </TabItem>
</Tabs>

#### Example: Remove a custom field

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token update-metadata 5K8RVdjpY3CHujyKjQ7RkyiCJqTG8Kba9krNfpZnmvpS new-field --remove
Signature: 52s1mxRqnr2jcZNvcmcgsQuXfVyT2w1TuRsEE3J6YwEZBu74BbFcHh2DvwnJG7qC7Cy6C5ZrTfnoPREFjFS7kXjF
```
  </TabItem>
  <TabItem value="jsx" label="JS">

```js
import { createRemoveKeyInstruction } from "@solana/spl-token-metadata";

(async () => {
  const tx = new Transaction().add(
    createRemoveKeyInstruction({
      programId: TOKEN_2022_PROGRAM_ID,
      metadata: mint.publicKey,
      updateAuthority: payer.publicKey,
      key: 'new-field',
      idempotent: true, // If false the operation will fail if the field does not exist in the metadata
    }),
  );
  await sendAndConfirmTransaction(connection, tx, [ payer, mint ]);
})();
```

  </TabItem>
</Tabs>

### Group Pointer

Similar to the metadata pointer, the group pointer allows a token creator to
designate a group account that describes the mint. However, rather than
describing token metadata, the group account describes configurations for
grouping tokens together.

When a Token-2022 mint possesses a group pointer, the mint is considered to be
a group mint (for example a Collection NFT). Group mints have configurations
that allow them to be used as a point of reference for a related set of tokens.

Similar to metadata, the group pointer can point to the mint itself, and a
client must check that the mint and the group both point to each other.

#### Example: Create a mint with a group pointer to an external account

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --group-address 7ZJVSav7y76M41eFeyA3xz39UDigQspVNwyJ469TgR1S
Creating token EUMhJgfvjZa7Lb7fSqfD6WCUwELzzRVKunKSnSi4xK42 under program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

Address:  EUMhJgfvjZa7Lb7fSqfD6WCUwELzzRVKunKSnSi4xK42
Decimals:  9

Signature: 3ug4Ejs16jJgEm1WyBwDDxzh9xqPzQ3a2cmy1hSYiPFcLQi9U12HYF1Dbhzb2bx75SSydfU6W4e11dGUXaPbJqVc
```

  </TabItem>
  <TabItem value="jsx" label="JS">

Coming soon!

  </TabItem>
</Tabs>

### Group

Token-2022 supports grouping of tokens through the group extension. The
configurations for a group, which describe things like the update authority
and the group's maximum size, can be stored directly in the mint itself.

Token-2022 implements all of the instructions from the
[spl-token-group-interface](https://github.com/solana-labs/solana-program-library/tree/master/token-group/interface).

The group extension works directly with the group-pointer extension.
To initialize group configurations within a mint, you must add the group-pointer
extension, pointed at the mint itself, during mint creation.

The tools do this for you automatically.

#### Example: Create a mint with group configurations

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --enable-group
Creating token 812A34SxxYx9KqFwUNAuW7Wpwtmuj2pc5u1TGQcvPnj3 under program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
To initialize group configurations inside the mint, please run `spl-token initialize-group 812A34SxxYx9KqFwUNAuW7Wpwtmuj2pc5u1TGQcvPnj3 <MAX_SIZE>`, and sign with the mint authority.

Address:  812A34SxxYx9KqFwUNAuW7Wpwtmuj2pc5u1TGQcvPnj3
Decimals:  9

Signature: 2BZH8KE7zVcBj7Mmnu6uCM9NT4ey7qHasZmEk6Bt3tyx1wKCXS3JtcgEvrXXEMFB5numQgA9wvR67o2Z4YQdEw7m

$ spl-token initialize-group 812A34SxxYx9KqFwUNAuW7Wpwtmuj2pc5u1TGQcvPnj3 12 --update-authority 3pGiHDDek35npQuyWQ7FGcWxqJdHvVPDHDDmBFs2YxQj
Signature: 2H16XtBqdwSbvvq8g5o2jhy4TknP6zgt71KHawEdyPvNuvusQrV4dPccUrMqjFeNTbk75AtzmzUVueH3yWiTjBCG
```

  </TabItem>
  <TabItem value="jsx" label="JS">

Coming soon!

  </TabItem>
</Tabs>

### Member Pointer

Similar to the metadata pointer and group pointer, the member pointer allows a
token creator to designate a member account that describes the mint. This
pointer describes configurations for a mint's membership of a group.

When a Token-2022 mint possesses a member pointer, the mint is considered to be
a member mint (for example an NFT that belongs to a collection).

Similar to metadata and group, the member pointer can point to the mint itself,
and a client must check that the mint and the member both point to each other.

#### Example: Create a mint with a member pointer to an external account

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --member-address CXWuFdWifFQSvMMZ3UxZZVKtjYi2bZt89f5v3yV8zdVE
Creating token 5anZzJbbj6rBkrXW7zzw7MH28xXufj7AB5oKX1Cv4fdh under program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

Address:  5anZzJbbj6rBkrXW7zzw7MH28xXufj7AB5oKX1Cv4fdh
Decimals:  9

Signature: 3ug4Ejs16jJgEm1WyBwDDxzh9xqPzQ3a2cmy1hSYiPFcLQi9U12HYF1Dbhzb2bx75SSydfU6W4e11dGUXaPbJqVc
```

  </TabItem>
  <TabItem value="jsx" label="JS">

Coming soon!

  </TabItem>
</Tabs>

### Member

The member extension also plays a key role in managing groups of tokens with
Token-2022. The configurations for a member, which describe things like the
group address and the member's number, can be stored directly in the mint
itself.

The member extension, like the group extension, works directly with the
member-pointer extension. To initialize member configurations within a mint,
you must add the member-pointer extension, pointed at the mint itself, during
mint creation.

The tools do this for you automatically.

#### Example: Create a mint with member configurations

<Tabs className="unique-tabs" groupId="language-selection">
  <TabItem value="cli" label="CLI" default>

```console
$ spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --enable-member
Creating token 9uyqmf9Ued4yQKi4hXT5wMzPF5Nv1S6skAjkjxcCaAyV under program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
To initialize group member configurations inside the mint, please run `spl-token initialize-member 9uyqmf9Ued4yQKi4hXT5wMzPF5Nv1S6skAjkjxcCaAyV`, and sign with the mint authority.

Address:  9uyqmf9Ued4yQKi4hXT5wMzPF5Nv1S6skAjkjxcCaAyV
Decimals:  9

Signature: 2BZH8KE7zVcBj7Mmnu6uCM9NT4ey7qHasZmEk6Bt3tyx1wKCXS3JtcgEvrXXEMFB5numQgA9wvR67o2Z4YQdEw7m

$ spl-token initialize-member 9uyqmf9Ued4yQKi4hXT5wMzPF5Nv1S6skAjkjxcCaAyV --update-authority 3pGiHDDek35npQuyWQ7FGcWxqJdHvVPDHDDmBFs2YxQj
Signature: 2H16XtBqdwSbvvq8g5o2jhy4TknP6zgt71KHawEdyPvNuvusQrV4dPccUrMqjFeNTbk75AtzmzUVueH3yWiTjBCG
```

  </TabItem>
  <TabItem value="jsx" label="JS">

Coming soon!

  </TabItem>
</Tabs>
